package edu.upol.paradise.compiler.parser;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import edu.upol.paradise.compiler.types.AnnotatedCons;
import edu.upol.paradise.compiler.types.Nil;
import edu.upol.paradise.compiler.types.ParadiseFixnum;
import edu.upol.paradise.compiler.types.ParadiseFloat;
import edu.upol.paradise.compiler.types.ParadiseObject;
import edu.upol.paradise.compiler.types.ParadisePair;
import edu.upol.paradise.compiler.types.ParadiseString;
import edu.upol.paradise.compiler.types.ParadiseSymbol;

public class ParadiseParser {
	static final Logger log = Logger.getLogger(ParadiseParser.class);

	private LookAheadIterator<Token> tokenSource;
	
	public ParadiseParser(LookAheadIterator<Token> tokens){
		tokenSource = tokens;
	}
	
	public ParadiseParser() {
		
	}
	
	public void addTokenSource(LookAheadIterator<Token> tokens){
		tokenSource = tokens;
	}

	public List<ParadisePair> toASTTree() throws ParseException {
		List<ParadisePair> trees = new ArrayList<ParadisePair>();
		while (tokenSource.hasNext()){
			if (tokenSource.nextTokenType() == TokenType.EOF)
				break;
			trees.add(loadTree());
		}
		return trees;
	}

	private ParadisePair loadTree() throws ParseException {
		return loadPair();
	}

	private ParadisePair loadPair() throws ParseException {
		ParadisePair ppair = doLoadPair();
		if (ppair.equals(Nil.NIL))
			return Nil.NIL;
		return ppair;
	}

	private ParadisePair doLoadPair() throws ParseException {
		Token t = tokenSource.next();
		if (t.getTokenType() != TokenType.LEFT_BRACKET)
			throw new ParseException("Expected ( got " + t.getTokenValue());
		AnnotatedCons current = new AnnotatedCons();
		AnnotatedCons previous = current;
		ParadisePair head = current;
		boolean pair = false;
		boolean endDot = false;
		boolean emptyList = true;
		while (true){
			TokenType tt = tokenSource.nextTokenType();
			if (tt == TokenType.RIGHT_BRACKET){
				tokenSource.next();
				if (!endDot)
					previous.setCdr(Nil.NIL);
				if (emptyList){
					return Nil.NIL;
				}
				return head;
			}
			
			emptyList = false;
			
			if (endDot){
				throw new ParseException("Only one element can be following .");
			}
			
			if (tt == TokenType.DOT){
				pair = true;
				tokenSource.next();
				continue;
			}
			
			ParadiseObject next;
			int lineNo = tokenSource.nextTokenLineNo();
			int charNo = tokenSource.nextTokenCharNo();
			
			if (tt == TokenType.LEFT_BRACKET){
				next = loadPair();
			} else if (tt == TokenType.QUOTE){
				Token ttok = tokenSource.next();
				next = new AnnotatedCons();
				((AnnotatedCons)next).setCar(new ParadiseSymbol("QUOTE"), ttok.getLineNo(), ttok.getCharNo());
				AnnotatedCons nnext = new AnnotatedCons();
				((AnnotatedCons)next).setCdr(nnext);
				
				tt = tokenSource.nextTokenType();
				if (tt == TokenType.LEFT_BRACKET){
					int llineNo = tokenSource.nextTokenLineNo();
					int ccharNo = tokenSource.nextTokenCharNo();
					nnext.setCar(loadPair(), llineNo, ccharNo);
				} else if (tt == TokenType.QUOTE){
					throw new ParseException("Nested quotes are not allowed (" + tokenSource.nextTokenLineNo() + ": " + tokenSource.nextTokenCharNo() + ")");
				} else {
					int llineNo = tokenSource.nextTokenLineNo();
					int ccharNo = tokenSource.nextTokenCharNo();
					nnext.setCar(loadAtom(), llineNo, ccharNo);
				}
			} else {
				next = loadAtom();
			}
			
			if (!pair){
				current.setCar(next, lineNo, charNo);
				current.setCdr(new AnnotatedCons());
				previous = current;
				current = (AnnotatedCons) current.cdr();
			} else {
				endDot = true;
				previous.setCdr(next);
			}
		}
	}

	private ParadiseObject loadAtom() throws ParseException{
		Token t = tokenSource.next();
		
		switch (t.getTokenType()){
		case INT:
			return new ParadiseFixnum((Integer) t.getTokenValue());
		case FLOAT:
			return new ParadiseFloat((Double)t.getTokenValue());
		case QUOTED_STRING:
			return new ParadiseString((String)t.getTokenValue());
		case STRING:
			return new ParadiseSymbol(((String)t.getTokenValue()).toUpperCase());
		case EOF:
			break;
		default:
			break;
		} 
		
		throw new ParseException("Unexpected EOF");
	}

}
